module ESM

  !-----------------------------------------------------------------------------
  ! Code that specializes generic ESM Component code.
  !-----------------------------------------------------------------------------

  use shr_kind_mod , only : r8=>shr_kind_r8, cl=>shr_kind_cl, cs=>shr_kind_cs
  use shr_log_mod  , only : shrlogunit=> shr_log_unit
  use shr_sys_mod  , only : shr_sys_abort
  use shr_mpi_mod  , only : shr_mpi_bcast
  use shr_mem_mod  , only : shr_mem_init
  use shr_file_mod , only : shr_file_setLogunit
  use esm_utils_mod, only : logunit, mastertask, dbug_flag, chkerr
  use perf_mod     , only : t_initf

  implicit none
  private

  public  :: SetServices
  public  :: ReadAttributes ! used in ensemble_driver

  private :: SetModelServices
  private :: SetRunSequence
  private :: ModifyCplLists
  private :: InitAttributes
  private :: CheckAttributes
  private :: AddAttributes
  private :: InitAdvertize
  private :: esm_init_pelayout
  private :: esm_finalize
  private :: pretty_print_nuopc_freeformat

  character(*), parameter :: u_FILE_u = &
       __FILE__

!================================================================================
contains
!================================================================================

  subroutine SetServices(driver, rc)

    use NUOPC        , only : NUOPC_CompDerive, NUOPC_CompSpecialize, NUOPC_CompSetInternalEntryPoint
    use NUOPC_Driver , only : driver_routine_SS             => SetServices
    use NUOPC_Driver , only : driver_label_SetModelServices => label_SetModelServices
    use NUOPC_Driver , only : driver_label_SetRunSequence   => label_SetRunSequence
    use NUOPC_Driver , only : driver_label_Finalize         => label_Finalize
    use ESMF         , only : ESMF_GridComp, ESMF_Config, ESMF_GridCompSet, ESMF_ConfigLoadFile
    use ESMF         , only : ESMF_METHOD_INITIALIZE
    use ESMF         , only : ESMF_SUCCESS, ESMF_LogWrite, ESMF_LOGMSG_INFO

    ! input/output variables
    type(ESMF_GridComp)  :: driver
    integer, intent(out) :: rc

    ! local variables
    type(ESMF_Config) :: runSeq
    character(len=*), parameter :: subname = "(esm.F90:SetServices)"
    !---------------------------------------

    rc = ESMF_SUCCESS
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

    ! NUOPC_Driver registers the generic methods
    call NUOPC_CompDerive(driver, driver_routine_SS, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    ! attach specializing method(s)
    call NUOPC_CompSpecialize(driver, specLabel=driver_label_SetModelServices, &
         specRoutine=SetModelServices, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call NUOPC_CompSpecialize(driver, specLabel=driver_label_SetRunSequence, &
         specRoutine=SetRunSequence, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    ! register an internal initialization method
    call NUOPC_CompSetInternalEntryPoint(driver, ESMF_METHOD_INITIALIZE, &
         phaseLabelList=(/"IPDv03p2"/), userRoutine=ModifyCplLists, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    !
    ! This prevents the driver trying to "auto" connect to the ensemble_driver
    ! by default the FieldTransferPolicy is "transferall" and we need "transfernone"
    !
    call NUOPC_CompSetInternalEntryPoint(driver, ESMF_METHOD_INITIALIZE, &
      phaseLabelList=(/"IPDv05p1"/), userRoutine=InitAdvertize, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    ! Set a finalize method
    call NUOPC_CompSpecialize(driver, specLabel=driver_label_Finalize, &
         specRoutine=esm_finalize, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    ! Create, open and set the config

    call ESMF_GridCompSet(driver, configFile="nuopc.runconfig", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ESMF_LogWrite(trim(subname)//": done", ESMF_LOGMSG_INFO)

  end subroutine SetServices

  !================================================================================

  subroutine SetModelServices(driver, rc)

    use ESMF         , only : ESMF_GridComp, ESMF_VM, ESMF_Config, ESMF_VMBarrier
    use ESMF         , only : ESMF_GridCompGet, ESMF_VMGet, ESMF_ConfigGetAttribute
    use ESMF         , only : ESMF_ConfigGetLen, ESMF_RC_NOT_VALID, ESMF_LogFoundAllocError
    use ESMF         , only : ESMF_LogSetError, ESMF_LogWrite, ESMF_LOGMSG_INFO
    use ESMF         , only : ESMF_GridCompSet, ESMF_SUCCESS, ESMF_METHOD_INITIALIZE
    use ESMF         , only : ESMF_VMisCreated, ESMF_GridCompIsPetLocal
    use ESMF         , only : ESMF_RC_FILE_OPEN, ESMF_RC_FILE_READ
    use ESMF         , only : ESMF_AttributeUpdate, ESMF_VMBroadcast
    use ESMF         , only : ESMF_MethodAdd
    use NUOPC        , only : NUOPC_CompSetInternalEntryPoint, NUOPC_CompAttributeGet
    use NUOPC        , only : NUOPC_CompAttributeAdd, NUOPC_CompAttributeSet
    use NUOPC_Driver , only : NUOPC_DriverAddComp, NUOPC_DriverGetComp

    ! input/output variables
    type(ESMF_GridComp)    :: driver
    integer, intent(out)   :: rc

    ! local variables
    type(ESMF_VM)     :: vm
    type(ESMF_Config) :: config
    integer           :: n, i, stat
    character(len=20) :: model, prefix
    integer           :: localPet, medpet
    character(len=CL) :: meminitStr
    integer           :: global_comm
    integer           :: maxthreads
    character(len=CL) :: msgstr
    integer           :: componentcount
    character(len=*), parameter :: subname = "(esm.F90:SetModelServices)"
    !-------------------------------------------

    rc = ESMF_SUCCESS
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

    !-------------------------------------------
    ! Set the io logunit to the value defined in ensemble_driver
    ! TODO: - is this statement still correct?
    ! it may be corrected below if the med mastertask is not the driver mastertask
    !-------------------------------------------
    call shr_file_setLogunit(logunit)

    !-------------------------------------------
    ! Get the config and vm objects from the driver
    !-------------------------------------------

    call ESMF_GridCompGet(driver, vm=vm, config=config, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ESMF_VMGet(vm, localPet=localPet, mpiCommunicator=global_comm, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ESMF_VMGet(vm, localPet=localPet, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    if (localPet == 0) then
       mastertask=.true.
    else
       mastertask = .false.
    end if

    !-------------------------------------------
    ! determine the generic component labels
    !-------------------------------------------

    componentCount = ESMF_ConfigGetLen(config,label="component_list:", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    if (componentCount == 0) then
      write (msgstr, *) "No models were specified in component_list "
      call ESMF_LogSetError(ESMF_RC_NOT_VALID, msg=msgstr, line=__LINE__, file=__FILE__, rcToReturn=rc)
      return  ! bail out
    endif

    !-------------------------------------------
    ! Obtain driver attributes
    !-------------------------------------------

    call ReadAttributes(driver, config, "DRIVER_attributes::", formatprint=.true., rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ReadAttributes(driver, config, "CLOCK_attributes::", formatprint=.true., rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ReadAttributes(driver, config, "ALLCOMP_attributes::", formatprint=.true., rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ReadAttributes(driver, config, "PELAYOUT_attributes::", formatprint=.true., rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call CheckAttributes(driver, rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    !-------------------------------------------
    ! Initialize other attributes (after initializing driver clock)
    !-------------------------------------------

    call InitAttributes(driver, rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    !-------------------------------------------
    ! Initialize component pe layouts
    !-------------------------------------------

    call esm_init_pelayout(driver, maxthreads, rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    ! Memory test
    if (mastertask) then
       call shr_mem_init(strbuf=meminitstr)
       write(logunit,*) trim(meminitstr)
    end if

    !-------------------------------------------
    ! Timer initialization (has to be after pelayouts are determined)
    !-------------------------------------------

    call t_initf('drv_in', LogPrint=.true., mpicom=global_comm, mastertask=mastertask, MaxThreads=maxthreads)

    call ESMF_LogWrite(trim(subname)//": done", ESMF_LOGMSG_INFO)

  end subroutine SetModelServices

  !================================================================================

  subroutine SetRunSequence(driver, rc)

    use ESMF         , only : ESMF_GridComp, ESMF_LogWrite, ESMF_SUCCESS, ESMF_LOGMSG_INFO
    use ESMF         , only : ESMF_Config
    use ESMF         , only : ESMF_GridCompGet, ESMF_ConfigCreate
    use ESMF         , only : ESMF_ConfigLoadFile
    use NUOPC        , only : NUOPC_FreeFormat, NUOPC_FreeFormatDestroy
    use NUOPC        , only : NUOPC_FreeFormatCreate
    use NUOPC_Driver , only : NUOPC_DriverIngestRunSequence, NUOPC_DriverSetRunSequence
    use NUOPC_Driver , only : NUOPC_DriverPrint

    ! input/output variables
    type(ESMF_GridComp)  :: driver
    integer, intent(out) :: rc

    ! local variables
    integer                 :: localrc
    type(ESMF_Config)       :: runSeq
    type(NUOPC_FreeFormat)  :: runSeqFF
    character(len=*), parameter :: subname = "(esm.F90:SetRunSequence)"
    !---------------------------------------

    rc = ESMF_SUCCESS
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

    !--------
    ! Run Sequence and Connectors
    !--------

    ! read free format run sequence

    runSeq = ESMF_ConfigCreate(rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ESMF_ConfigLoadFile(runSeq, "nuopc.runseq", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    runSeqFF = NUOPC_FreeFormatCreate(runSeq, label="runSeq::", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call NUOPC_DriverIngestRunSequence(driver, runSeqFF, autoAddConnectors=.true., rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    ! Uncomment these to add debugging information for driver
    ! call NUOPC_DriverPrint(driver, orderflag=.true.)
    ! if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, &
    !   line=__LINE__, &
    !   file=__FILE__)) &
    !   return  ! bail out

    ! call pretty_print_nuopc_freeformat(runSeqFF, 'run sequence', rc=rc)
    ! if (chkerr(rc,__LINE__,u_FILE_u)) return

    call NUOPC_FreeFormatDestroy(runSeqFF, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ESMF_LogWrite(trim(subname)//": done", ESMF_LOGMSG_INFO)

  end subroutine SetRunSequence

  !================================================================================

  subroutine pretty_print_nuopc_freeformat(ffstuff, label, rc)

    use NUOPC, only : NUOPC_FreeFormat, NUOPC_FreeFormatGet, NUOPC_FreeFormatLen
    use ESMF,  only : ESMF_SUCCESS

    ! input/output variables
    type(NUOPC_FreeFormat) , intent(in)  :: ffstuff
    character(len=*)       , intent(in)  :: label
    integer                , intent(out) :: rc

    ! local variables
    integer :: i
    integer :: linecnt
    character(len=NUOPC_FreeFormatLen), pointer :: outstr(:)
    !---------------------------------------

    rc = ESMF_SUCCESS

    if (mastertask .or. dbug_flag > 3) then
       write(logunit, *) 'BEGIN: ', trim(label)
       call NUOPC_FreeFormatGet(ffstuff, linecount=linecnt, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       allocate(outstr(linecnt))
       call NUOPC_FreeFormatGet(ffstuff, stringList=outstr, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       do i=1,linecnt
          if(len_trim(outstr(i)) > 0) then
             write(logunit, *) trim(outstr(i))
          endif
       enddo
       write(logunit, *) 'END: ', trim(label)
       deallocate(outstr)
    endif

  end subroutine pretty_print_nuopc_freeformat

  !================================================================================

  recursive subroutine ModifyCplLists(driver, importState, exportState, clock, rc)

    use ESMF         , only : ESMF_GridComp, ESMF_State, ESMF_Clock, ESMF_LogWrite
    use ESMF         , only : ESMF_LOGMSG_INFO, ESMF_CplComp, ESMF_SUCCESS
    use NUOPC        , only : NUOPC_CompAttributeGet, NUOPC_CompAttributeSet
    use NUOPC_Driver , only : NUOPC_DriverGetComp

    type(ESMF_GridComp)  :: driver
    type(ESMF_State)     :: importState, exportState
    type(ESMF_Clock)     :: clock
    integer, intent(out) :: rc

    type(ESMF_CplComp), pointer    :: connectorList(:)
    integer                        :: i, j, cplListSize
    character(len=CL), allocatable :: cplList(:)
    character(len=CL)              :: tempString
    character(len=CL)              :: msgstr
    character(len=*), parameter    :: subname = "(esm.F90:ModifyCplLists)"
    !---------------------------------------

    rc = ESMF_SUCCESS
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

    call ESMF_LogWrite("Driver is in ModifyCplLists()", ESMF_LOGMSG_INFO, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    nullify(connectorList)
    call NUOPC_DriverGetComp(driver, compList=connectorList, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    write (msgstr,*) "Found ", size(connectorList), " Connectors."// " Modifying CplList Attribute...."
    call ESMF_LogWrite(trim(msgstr), ESMF_LOGMSG_INFO, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    do i=1, size(connectorList)

      ! query the cplList for connector i
      call NUOPC_CompAttributeGet(connectorList(i), name="CplList", itemCount=cplListSize, rc=rc)
      if (chkerr(rc,__LINE__,u_FILE_u)) return

      if (cplListSize>0) then
        allocate(cplList(cplListSize))

        call NUOPC_CompAttributeGet(connectorList(i), name="CplList", valueList=cplList, rc=rc)
        if (chkerr(rc,__LINE__,u_FILE_u)) return

        ! go through all of the entries in the cplList and set the mapping method to "redist"
        do j=1, cplListSize
           !tempString = trim(cplList(j))//":REMAPMETHOD=bilinear"//&
           !":SrcTermProcessing=1:DUMPWEIGHTS=true:TermOrder=SrcSeq"

           tempString = trim(cplList(j))//":remapmethod=redist"
           cplList(j) = trim(tempString)
        enddo

        ! store the modified cplList in CplList attribute of connector i
        call NUOPC_CompAttributeSet(connectorList(i), name="CplList", valueList=cplList, rc=rc)
        if (chkerr(rc,__LINE__,u_FILE_u)) return

        deallocate(cplList)
      endif
    enddo

    deallocate(connectorList)

    call ESMF_LogWrite(trim(subname)//": done", ESMF_LOGMSG_INFO)

  end subroutine ModifyCplLists

  !================================================================================

  subroutine InitAttributes(driver, rc)

    use ESMF             , only : ESMF_GridComp, ESMF_GridCompGet
    use ESMF             , only : ESMF_Clock, ESMF_ClockGet, ESMF_Time, ESMF_TimeGet
    use ESMF             , only : ESMF_SUCCESS, ESMF_LogWrite, ESMF_LogSetError, ESMF_LOGMSG_INFO
    use ESMF             , only : ESMF_RC_NOT_VALID
    use ESMF             , only : ESMF_GridCompIsPetLocal, ESMF_VMBroadcast
    use NUOPC            , only : NUOPC_CompAttributeGet, NUOPC_CompAttributeSet, NUOPC_CompAttributeAdd
    use shr_assert_mod   , only : shr_assert_in_domain
    use shr_const_mod    , only : shr_const_tkfrz, shr_const_tktrip
    use shr_const_mod    , only : shr_const_mwwv, shr_const_mwdair
    use shr_frz_mod      , only : shr_frz_freezetemp_init
    use shr_reprosum_mod , only : shr_reprosum_setopts
    use shr_wv_sat_mod   , only : shr_wv_sat_set_default, shr_wv_sat_init
    use shr_wv_sat_mod   , only : shr_wv_sat_make_tables, ShrWVSatTableSpec
    use shr_wv_sat_mod   , only : shr_wv_sat_get_scheme_idx, shr_wv_sat_valid_idx
    use glc_elevclass_mod, only : glc_elevclass_init
   !use shr_scam_mod     , only : shr_scam_checkSurface

    ! input/output variables
    type(ESMF_GridComp) , intent(inout) :: driver
    integer             , intent(out)   :: rc                 ! return code

    ! local variables
    character(len=CL)            :: errstring
    character(len=CL)            :: cvalue
    logical                      :: reprosum_use_ddpdd    ! setup reprosum, use ddpdd
    real(R8)                     :: reprosum_diffmax      ! setup reprosum, set rel_diff_max
    logical                      :: reprosum_recompute    ! setup reprosum, recompute if tolerance exceeded
    character(LEN=CS)            :: tfreeze_option        ! Freezing point calculation
    real(R8)                     :: wall_time_limit       ! wall time limit in hours
    integer                      :: glc_nec               ! number of elevation classes in the land component for lnd->glc
    logical                      :: single_column         ! scm mode logical
    real(R8)                     :: scmlon                ! single column lon
    real(R8)                     :: scmlat                ! single column lat
    character(LEN=CS)            :: wv_sat_scheme
    real(R8)                     :: wv_sat_transition_start
    logical                      :: wv_sat_use_tables
    real(R8)                     :: wv_sat_table_spacing
    type(ShrWVSatTableSpec)      :: liquid_spec
    type(ShrWVSatTableSpec)      :: ice_spec
    type(ShrWVSatTableSpec)      :: mixed_spec
    logical                      :: flag
    integer                      :: i, it, n
    integer                      :: unitn                 ! Namelist unit number to read
    integer                      :: localPet, rootpe_med
    character(len=CL)            :: msgstr
    integer          , parameter :: ens1=1                ! use first instance of ensemble only
    integer          , parameter :: fix1=1                ! temporary hard-coding to first ensemble, needs to be fixed
    real(R8)         , parameter :: epsilo = shr_const_mwwv/shr_const_mwdair
    character(len=*) , parameter :: subname = '(InitAttributes)'
    !----------------------------------------------------------

    rc = ESMF_SUCCESS
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

    !----------------------------------------------------------
    ! Initialize options for reproducible sums
    ! TODO: this needs to be moved out of here
    !----------------------------------------------------------

    call NUOPC_CompAttributeGet(driver, name="reprosum_use_ddpdd", value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) reprosum_use_ddpdd

    call NUOPC_CompAttributeGet(driver, name="reprosum_diffmax", value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) reprosum_diffmax

    call NUOPC_CompAttributeGet(driver, name="reprosum_recompute", value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) reprosum_recompute

    call shr_reprosum_setopts(repro_sum_use_ddpdd_in=reprosum_use_ddpdd, &
         repro_sum_rel_diff_max_in=reprosum_diffmax, repro_sum_recompute_in=reprosum_recompute)

    !----------------------------------------------------------
    ! Initialize freezing point calculation for all components
    ! TODO: this needs to be moved out of here
    !----------------------------------------------------------

    call NUOPC_CompAttributeGet(driver, name="tfreeze_option", value=tfreeze_option, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call shr_frz_freezetemp_init(tfreeze_option, mastertask)


    call NUOPC_CompAttributeGet(driver, name='cpl_rootpe', value=cvalue, rc=rc)
    read(cvalue, *) rootpe_med
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    call ESMF_GridCompGet(driver, localPet=localPet, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    !----------------------------------------------------------
    ! Initialize glc_elevclass_mod module variables
    !----------------------------------------------------------

    ! This must be called on all processors of the driver

    call NUOPC_CompAttributeGet(driver, name='glc_nec', value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) glc_nec

    call glc_elevclass_init(glc_nec, logunit=logunit)

    !----------------------------------------------------------
    ! Initialize water vapor info
    !----------------------------------------------------------

    ! TODO: this does not seem to belong here - where should it go?

    call NUOPC_CompAttributeGet(driver, name="wv_sat_scheme", value=wv_sat_scheme, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    if (.not. shr_wv_sat_valid_idx(shr_wv_sat_get_scheme_idx(trim(wv_sat_scheme)))) then
       call shr_sys_abort(subname//': "'//trim(wv_sat_scheme)//'" is not a recognized saturation vapor pressure scheme name')
    end if
    if (.not. shr_wv_sat_set_default(wv_sat_scheme)) then
       call shr_sys_abort('Invalid wv_sat_scheme.')
    end if

    call NUOPC_CompAttributeGet(driver, name="wv_sat_transition_start", value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) wv_sat_transition_start

    call shr_assert_in_domain(wv_sat_transition_start, &
         ge=0._R8, le=40._R8, &
         varname="wv_sat_transition_start", msg="Invalid transition temperature range.")

    call NUOPC_CompAttributeGet(driver, name="wv_sat_use_tables", value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) wv_sat_use_tables

    call NUOPC_CompAttributeGet(driver, name="wv_sat_table_spacing", value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) wv_sat_table_spacing

    ! A transition range averaging method in CAM is only valid for:
    ! -40 deg C <= T <= 0 deg C
    ! shr_wv_sat_mod itself checks for values with the wrong sign, but we
    ! have to check that the range is no more than 40 deg C here. Even
    ! though this is a CAM-specific restriction, it's not really likely
    ! that any other parameterization will be dealing with mixed-phase
    ! water below 40 deg C anyway.

    call shr_wv_sat_init(shr_const_tkfrz, shr_const_tktrip, wv_sat_transition_start, epsilo, errstring)
    if (errstring /= "") then
       call shr_sys_abort('shr_wv_sat_init: '//trim(errstring))
    end if

    ! The below produces internal lookup tables in the range 175-374K for
    ! liquid water, and 125-274K for ice, with a resolution set by the
    ! option wv_sat_table_spacing.
    ! In theory these ranges could be specified in the namelist, but in
    ! practice users will want to change them *very* rarely if ever, which
    ! is why only the spacing is in the namelist.

    if (wv_sat_use_tables) then
       liquid_spec = ShrWVSatTableSpec(ceiling(200._R8/wv_sat_table_spacing), 175._R8, wv_sat_table_spacing)
       ice_spec    = ShrWVSatTableSpec(ceiling(150._R8/wv_sat_table_spacing), 125._R8, wv_sat_table_spacing)
       mixed_spec  = ShrWVSatTableSpec(ceiling(250._R8/wv_sat_table_spacing), 125._R8, wv_sat_table_spacing)
       call shr_wv_sat_make_tables(liquid_spec, ice_spec, mixed_spec)
    end if

    !----------------------------------------------------------
    ! Set single_column flags
    ! If in single column mode, overwrite flags according to focndomain file
    ! in ocn_in namelist. SCAM can reset the "present" flags for lnd,
    ! ocn, ice, rof, and flood.
    !----------------------------------------------------------

    call NUOPC_CompAttributeGet(driver, name="single_column", value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) single_column

    ! NOTE: cam stand-alone aqua-planet model will no longer be supported here - only the data model aqua-planet
    ! will be supported
    if (single_column) then

       call NUOPC_CompAttributeGet(driver, name="scmlon", value=cvalue, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       read(cvalue,*) scmlon

       call NUOPC_CompAttributeGet(driver, name="scmlat", value=cvalue, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       read(cvalue,*) scmlat

       ! TODO(mvertens, 2019-01-30): need to add single column functionality

    endif

  end subroutine InitAttributes

  !================================================================================

  subroutine CheckAttributes( driver, rc )

    ! !DESCRIPTION: Check that input driver config values have reasonable values

    use ESMF        , only : ESMF_GridComp, ESMF_SUCCESS, ESMF_LogWrite, ESMF_LOGMSG_INFO
    use NUOPC       , only : NUOPC_CompAttributeGet

    ! !INPUT/OUTPUT PARAMETERS:
    type(ESMF_GridComp) , intent(inout) :: driver
    integer             , intent(out)   :: rc

    !----- local -----
    character(len=CL) :: cvalue         ! temporary
    character(len=CL) :: start_type     ! Type of startup
    character(len=CS) :: logFilePostFix ! postfix for output log files
    character(len=CL) :: outPathRoot    ! root for output log files
    character(len=CS) :: cime_model
    character(len=*), parameter :: subname = '(driver_attributes_check) '
    !-------------------------------------------------------------------------------

    rc = ESMF_SUCCESS
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

    call NUOPC_CompAttributeGet(driver, name="cime_model", value=cime_model, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    if ( trim(cime_model) /= 'cesm') then
       call shr_sys_abort( subname//': cime_model must be set to cesm, aborting')
    end if

    ! --- LogFile ending name -----
    call NUOPC_CompAttributeGet(driver, name="logFilePostFix", value=logFilePostFix, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    if ( len_trim(logFilePostFix) == 0 ) then
       call shr_sys_abort( subname//': logFilePostFix  must be set to something not blank' )
    end if

    ! --- Output path root directory -----
    call NUOPC_CompAttributeGet(driver, name="outPathRoot", value=outPathRoot, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    if ( len_trim(outPathRoot) == 0 ) then
       call shr_sys_abort( subname//': outPathRoot  must be set' )
    end if
    if ( index(outPathRoot, "/", back=.true.) /= len_trim(outPathRoot) ) then
       call shr_sys_abort( subname//': outPathRoot must end with a slash' )
    end if

  end subroutine CheckAttributes

  !===============================================================================

  subroutine AddAttributes(gcomp, driver, config, compid, compname, inst_suffix, rc)

    ! Add specific set of attributes to components from driver attributes

    use ESMF  , only : ESMF_GridComp, ESMF_Config, ESMF_LogWrite, ESMF_LOGMSG_INFO, ESMF_SUCCESS
    use ESMF  , only : ESMF_LogFoundAllocError, ESMF_ConfigGetLen, ESMF_ConfigGetAttribute
    use NUOPC , only : NUOPC_CompAttributeAdd, NUOPC_CompAttributeGet, NUOPC_CompAttributeSet

    ! input/output parameters
    type(ESMF_GridComp) , intent(inout) :: gcomp
    type(ESMF_GridComp) , intent(in)    :: driver
    type(ESMF_Config)   , intent(inout) :: config
    integer             , intent(in)    :: compid
    character(len=*)    , intent(in)    :: compname
    character(len=*)    , intent(in)    :: inst_suffix
    integer             , intent(inout) :: rc

    ! local variables
    integer                        :: n
    integer                        :: stat
    integer                        :: inst_index
    character(len=CL)              :: cvalue
    character(len=CS)              :: attribute
    integer                        :: componentCount
    character(len=*), parameter    :: subname = "(esm.F90:AddAttributes)"
    logical                        :: lvalue = .false.
    !-------------------------------------------

    rc = ESMF_Success
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

    !------
    ! Add compid to gcomp attributes
    !------
    write(cvalue,*) compid
    call NUOPC_CompAttributeAdd(gcomp, attrList=(/'MCTID'/), rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    call NUOPC_CompAttributeSet(gcomp, name='MCTID', value=trim(cvalue), rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    !------
    ! Add restart flag a to gcomp attributes
    !------
    attribute = 'read_restart'
    call NUOPC_CompAttributeAdd(gcomp, (/trim(attribute)/), rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    call NUOPC_CompAttributeGet(driver, name="mediator_read_restart", value=cvalue, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    read(cvalue,*) lvalue
    if (.not. lvalue) then         
       call NUOPC_CompAttributeGet(driver, name=trim(attribute), value=cvalue, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
    end if
    call NUOPC_CompAttributeSet(gcomp, name=trim(attribute), value=trim(cvalue), rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    !------
    ! Add component specific attributes
    !------
    call ReadAttributes(gcomp, config, trim(compname)//"_attributes::", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ReadAttributes(gcomp, config, "ALLCOMP_attributes::", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ReadAttributes(gcomp, config, trim(compname)//"_modelio"//trim(inst_suffix)//"::", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ReadAttributes(gcomp, config, "CLOCK_attributes::", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    !------
    ! Add mediator specific attributes - if component is mediator
    !------
    if (compname == 'MED') then
       call ReadAttributes(gcomp, config, "MED_attributes::", rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
    endif

    !------
    ! Add multi-instance specific attributes
    !------
    call NUOPC_CompAttributeAdd(gcomp, attrList=(/'inst_index'/), rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    ! add inst_index attribute (inst_index is not required for cime internal components)
    ! for now hard-wire inst_index to 1
    inst_index = 1
    write(cvalue,*) inst_index
    call NUOPC_CompAttributeSet(gcomp, name='inst_index', value=trim(cvalue), rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    ! add inst_suffix attribute
    if (len_trim(inst_suffix) > 0) then
       call NUOPC_CompAttributeAdd(gcomp, attrList=(/'inst_suffix'/), rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       call NUOPC_CompAttributeSet(gcomp, name='inst_suffix', value=inst_suffix, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
    end if

  end subroutine AddAttributes

  !================================================================================

  subroutine ReadAttributes(gcomp, config, label, relaxedflag, formatprint, rc)

    use ESMF  , only : ESMF_GridComp, ESMF_Config, ESMF_LogWrite, ESMF_LOGMSG_INFO, ESMF_SUCCESS
    use NUOPC , only : NUOPC_FreeFormatCreate, NUOPC_CompAttributeIngest
    use NUOPC , only : NUOPC_FreeFormatDestroy, NUOPC_FreeFormat

    ! input/output arguments
    type(ESMF_GridComp) , intent(inout)        :: gcomp
    type(ESMF_Config)   , intent(in)           :: config
    character(len=*)    , intent(in)           :: label
    logical             , intent(in), optional :: relaxedflag
    logical             , intent(in), optional :: formatprint
    integer             , intent(inout)        :: rc

    ! local variables
    type(NUOPC_FreeFormat)  :: attrFF
    character(len=*), parameter :: subname = "(esm.F90:ReadAttributes)"
    !-------------------------------------------

    rc = ESMF_SUCCESS

    if (present(relaxedflag)) then
       attrFF = NUOPC_FreeFormatCreate(config, label=trim(label), relaxedflag=.true., rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
    else
       attrFF = NUOPC_FreeFormatCreate(config, label=trim(label), rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
    end if

    call NUOPC_CompAttributeIngest(gcomp, attrFF, addFlag=.true., rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    !    if (present (formatprint)) then
    !       call pretty_print_nuopc_freeformat(attrFF, trim(label)//' attributes', rc=rc)
    !       if (chkerr(rc,__LINE__,u_FILE_u)) return
    !    end if

    call NUOPC_FreeFormatDestroy(attrFF, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

  end subroutine ReadAttributes

  !================================================================================

  subroutine InitAdvertize(driver, importState, exportState, clock, rc)

    ! This empty InitAdvertise is needed because it overrides the behavior
    ! of the default InitAdvertise inside the generic NUOPC_Driver.F90. The
    ! default behavior tries to mirror the fields up the hierarchy (i.e., up
    ! to the ensemble driver). This would be used if we needed to
    ! communicate between the ensemble members. Since we do not need that
    ! right now, we turn it off with this empty subroutine.

    use ESMF, only : ESMF_GridComp, ESMF_State, ESMF_Clock
    use ESMF, only : ESMF_LogWrite, ESMF_SUCCESS, ESMF_LOGMSG_INFO

    ! input/output variables
    type(ESMF_GridComp)  :: driver
    type(ESMF_State)     :: importState, exportState
    type(ESMF_Clock)     :: clock
    integer, intent(out) :: rc

    ! local variables
    character(len=*), parameter :: subname = "(esm.F90:InitAdvertize)"
    !---------------------------------------

    rc = ESMF_SUCCESS
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

  end subroutine InitAdvertize

  !================================================================================

  subroutine esm_init_pelayout(driver, maxthreads, rc)

    use ESMF         , only : ESMF_GridComp, ESMF_GridCompGet, ESMF_VM, ESMF_VMGet
    use ESMF         , only : ESMF_LogWrite, ESMF_SUCCESS, ESMF_LOGMSG_INFO, ESMF_Config
    use ESMF         , only : ESMF_ConfigGetLen, ESMF_LogFoundAllocError, ESMF_ConfigGetAttribute
    use ESMF         , only : ESMF_RC_NOT_VALID, ESMF_LogSetError
    use ESMF         , only : ESMF_GridCompIsPetLocal, ESMF_MethodAdd, ESMF_UtilStringLowerCase
    use NUOPC        , only : NUOPC_CompAttributeGet
    use NUOPC_Driver , only : NUOPC_DriverAddComp
    use mpi          , only : MPI_COMM_NULL
    use mct_mod      , only : mct_world_init
    use shr_pio_mod  , only : shr_pio_init2

#ifdef MED_PRESENT
    use med_internalstate_mod , only : med_id
    use med                   , only : MedSetServices => SetServices
#endif
#ifdef ATM_PRESENT
    use atm_comp_nuopc        , only : ATMSetServices => SetServices
#endif
#ifdef ICE_PRESENT
    use ice_comp_nuopc        , only : ICESetServices => SetServices
#endif
#ifdef LND_PRESENT
    use lnd_comp_nuopc        , only : LNDSetServices => SetServices
#endif
#ifdef OCN_PRESENT
    use ocn_comp_nuopc        , only : OCNSetServices => SetServices
#endif
#ifdef WAV_PRESENT
    use wav_comp_nuopc        , only : WAVSetServices => SetServices
#endif
#ifdef ROF_PRESENT
    use rof_comp_nuopc        , only : ROFSetServices => SetServices
#endif
#ifdef GLC_PRESENT
    use glc_comp_nuopc        , only : GLCSetServices => SetServices
#endif

    ! input/output variables
    type(ESMF_GridComp)            :: driver
    integer, intent(out)           :: maxthreads ! maximum number of threads any component
    integer, intent(out)           :: rc

    ! local variables
    type(ESMF_GridComp)            :: child
    type(ESMF_VM)                  :: vm
    type(ESMF_Config)              :: config
    integer                        :: componentcount
    integer                        :: PetCount
    integer                        :: LocalPet
    integer                        :: ntasks, rootpe, nthrds, stride
    integer                        :: ntask, cnt
    integer                        :: i
    integer                        :: stat
    character(len=32), allocatable :: compLabels(:)
    character(CS)                  :: namestr
    character(CL)                  :: msgstr
    integer, allocatable           :: petlist(:)
    integer, pointer               :: comms(:), comps(:)
    integer                        :: Global_Comm
    logical                        :: isPresent
    integer, allocatable           :: comp_comm_iam(:)
    logical, allocatable           :: comp_iamin(:)
    character(len=5)               :: inst_suffix
    character(CL)                  :: cvalue
    logical                        :: found_comp 
    character(len=*), parameter    :: subname = "(esm_pelayout.F90:esm_init_pelayout)"
    !---------------------------------------

    rc = ESMF_SUCCESS
    call ESMF_LogWrite(trim(subname)//": called", ESMF_LOGMSG_INFO)

    maxthreads = 1
    call ESMF_GridCompGet(driver, vm=vm, config=config, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ReadAttributes(driver, config, "PELAYOUT_attributes::", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call ESMF_VMGet(vm, petCount=petCount, mpiCommunicator=Global_Comm, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    componentCount = ESMF_ConfigGetLen(config,label="component_list:", rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    allocate(compLabels(componentCount), stat=stat)
    if (ESMF_LogFoundAllocError(statusToCheck=stat, msg="Allocation of compLabels failed.", &
         line=__LINE__, file=u_FILE_u, rcToReturn=rc)) return
    allocate(comp_iamin(componentCount), stat=stat)
    if (ESMF_LogFoundAllocError(statusToCheck=stat, msg="Allocation of compLabels failed.", &
         line=__LINE__, file=u_FILE_u, rcToReturn=rc)) return
    allocate(comp_comm_iam(componentCount), stat=stat)
    if (ESMF_LogFoundAllocError(statusToCheck=stat, msg="Allocation of compLabels failed.", &
         line=__LINE__, file=u_FILE_u, rcToReturn=rc)) return

    call ESMF_ConfigGetAttribute(config, valueList=compLabels, label="component_list:", count=componentCount, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call NUOPC_CompAttributeGet(driver, name="inst_suffix", isPresent=isPresent, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    if (isPresent) then
       call NUOPC_CompAttributeGet(driver, name="inst_suffix", value=inst_suffix, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
    else
       inst_suffix = ""
    endif

    allocate(comms(componentCount+1), comps(componentCount+1))
    comps(1) = 1
    comms(1) = Global_Comm
    do i=1,componentCount

       namestr = ESMF_UtilStringLowerCase(compLabels(i))
       if (namestr == 'med') namestr = 'cpl'
       call NUOPC_CompAttributeGet(driver, name=trim(namestr)//'_ntasks', value=cvalue, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       read(cvalue,*) ntasks

       if (ntasks < 0 .or. ntasks > PetCount) then
          write (msgstr, *) "Invalid NTASKS value specified for component: ",namestr, ' ntasks: ',ntasks
          call ESMF_LogSetError(ESMF_RC_NOT_VALID, msg=msgstr, line=__LINE__, file=__FILE__, rcToReturn=rc)
          return
       endif

       call NUOPC_CompAttributeGet(driver, name=trim(namestr)//'_nthreads', value=cvalue, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       read(cvalue,*) nthrds

       if(nthrds > maxthreads) maxthreads = nthrds

       call NUOPC_CompAttributeGet(driver, name=trim(namestr)//'_rootpe', value=cvalue, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       read(cvalue,*) rootpe
       if (rootpe < 0 .or. rootpe > PetCount) then
          write (msgstr, *) "Invalid Rootpe value specified for component: ",namestr, ' rootpe: ',rootpe
          call ESMF_LogSetError(ESMF_RC_NOT_VALID, msg=msgstr, line=__LINE__, file=__FILE__, rcToReturn=rc)
          return
       endif
       if(rootpe+ntasks > PetCount) then
          write (msgstr, *) "Invalid pelayout value specified for component: ",namestr, ' rootpe+ntasks: ',rootpe+ntasks
          call ESMF_LogSetError(ESMF_RC_NOT_VALID, msg=msgstr, line=__LINE__, file=__FILE__, rcToReturn=rc)
          return
       endif

       call NUOPC_CompAttributeGet(driver, name=trim(namestr)//'_pestride', value=cvalue, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
       read(cvalue,*) stride
       if (stride < 1 .or. rootpe+ntasks*stride > PetCount) then
          write (msgstr, *) "Invalid pestride value specified for component: ",namestr,&
               ' rootpe: ',rootpe, ' pestride: ', stride
          call ESMF_LogSetError(ESMF_RC_NOT_VALID, msg=msgstr, line=__LINE__, file=__FILE__, rcToReturn=rc)
          return
       endif

       if (allocated(petlist) .and. size(petlist) .ne. ntasks) then
          deallocate(petlist)
       endif
       if(.not. allocated(petlist)) then
          allocate(petlist(ntasks))
       endif

       cnt = 1
       do ntask = rootpe, (rootpe+ntasks*stride)-1, stride
          petlist(cnt) = ntask
          cnt = cnt + 1
       enddo

       comps(i+1) = i+1

       found_comp = .false.
#ifdef MED_PRESENT
       if (trim(compLabels(i)) == 'MED') then
          med_id = i + 1
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), MEDSetServices, petList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
#ifdef ATM_PRESENT
       if (trim(compLabels(i)) .eq. 'ATM') then
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), ATMSetServices, petList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
#ifdef LND_PRESENT
       if (trim(compLabels(i)) .eq. 'LND') then
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), LNDSetServices, PetList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
#ifdef OCN_PRESENT
       if (trim(compLabels(i)) .eq. 'OCN') then
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), OCNSetServices, PetList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
#ifdef ICE_PRESENT
       if (trim(compLabels(i)) .eq. 'ICE') then
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), ICESetServices, PetList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
#ifdef GLC_PRESENT
       if (trim(compLabels(i)) .eq. 'GLC') then
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), GLCSetServices, PetList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
#ifdef ROF_PRESENT
       if (trim(compLabels(i)) .eq. 'ROF') then
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), ROFSetServices, PetList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
#ifdef WAV_PRESENT
       if (trim(compLabels(i)) .eq. 'WAV') then
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), WAVSetServices, PetList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
#ifdef ESP_PRESENT
       if (trim(compLabels(i)) .eq. 'ESP') then
          call NUOPC_DriverAddComp(driver, trim(compLabels(i)), ESPSetServices, PetList=petlist, comp=child, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return
          found_comp = .true.
       end if
#endif
       if (.not. found_comp) then
          write(msgstr,*) 'No component ',trim(compLabels(i)),' found'
          call ESMF_LogSetError(ESMF_RC_NOT_VALID, msg=msgstr, line=__LINE__, file=__FILE__, rcToReturn=rc)
          return
       endif

       call AddAttributes(child, driver, config, i+1, trim(compLabels(i)), inst_suffix, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return

       if (ESMF_GridCompIsPetLocal(child, rc=rc)) then
          call ESMF_GridCompGet(child, vm=vm, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return

          call ESMF_VMGet(vm, mpiCommunicator=comms(i+1), localPet=comp_comm_iam(i), rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return

          call AddAttributes(child, driver, config, i+1, trim(compLabels(i)), inst_suffix, rc=rc)
          if (chkerr(rc,__LINE__,u_FILE_u)) return

          ! This code is not supported, we need an optional arg to NUOPC_DriverAddComp to include the
          ! per component thread count.  #3614572 in esmf_support
          ! call ESMF_GridCompSetVMMaxPEs(child, maxPeCountPerPet=nthrds, rc=rc)
          ! if (chkerr(rc,__LINE__,u_FILE_u)) return

          comp_iamin(i) = .true.
       else
          comms(i+1) = MPI_COMM_NULL
          comp_iamin(i) = .false.
       endif
    enddo

    ! Initialize MCT (this is needed for data models and cice prescribed capability)
    call mct_world_init(componentCount+1, GLOBAL_COMM, comms, comps)

    ! Initialize PIO
    call shr_pio_init2(comps(2:), compLabels, comp_iamin, comms(2:), comp_comm_iam)

    deallocate(petlist, comms, comps, comp_iamin, comp_comm_iam)

  end subroutine esm_init_pelayout

  !================================================================================

  subroutine esm_finalize(driver, rc)

    use ESMF     , only : ESMF_GridComp, ESMF_GridCompGet, ESMF_VM, ESMF_VMGet
    use ESMF     , only : ESMF_SUCCESS
    use NUOPC    , only : NUOPC_CompAttributeGet
    use perf_mod , only : t_prf, t_finalizef

    ! input/output variables
    type(ESMF_GridComp)  :: driver
    integer, intent(out) :: rc

    ! local variables
    character(len=CL)    :: timing_dir        ! timing directory
    character(len=5)     :: inst_suffix
    logical              :: isPresent
    type(ESMF_VM)        :: vm
    integer              :: mpicomm
    !---------------------------------------

    rc = ESMF_SUCCESS

    if (mastertask) then
       write(logunit,*)' SUCCESSFUL TERMINATION OF CESM'
    end if

    call ESMF_GridCompGet(driver, vm=vm, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    call ESMF_VMGet(vm, mpiCommunicator=mpicomm, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call NUOPC_CompAttributeGet(driver, name="timing_dir",value=timing_dir, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return

    call NUOPC_CompAttributeGet(driver, name="inst_suffix", isPresent=isPresent, rc=rc)
    if (chkerr(rc,__LINE__,u_FILE_u)) return
    if (isPresent) then
       call NUOPC_CompAttributeGet(driver, name="inst_suffix", value=inst_suffix, rc=rc)
       if (chkerr(rc,__LINE__,u_FILE_u)) return
    else
       inst_suffix = ""
    endif
    call t_prf(trim(timing_dir)//'/model_timing'//trim(inst_suffix), mpicom=mpicomm)

    call t_finalizef()

  end subroutine esm_finalize

end module ESM
